package com.fs.service;

import com.fs.entity.Customer;
import com.fs.entity.Friend;
import com.fs.entity.PurchaseRecord;

import com.fs.entity.PurchaseState;

import com.fs.entity.Room;
import com.fs.repository.CustomerRepository;
import com.fs.repository.FriendRepository;
import com.fs.repository.PurchaseRepository;
import com.fs.repository.RoomRepository;
import com.fs.service.bo.BoPageInfo;
import com.fs.service.bo.friend.BoFriend;
import com.fs.service.bo.purchase.*;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Log4j2
@Service
@Transactional
@Validated
public class PurchaseService {

    @PersistenceContext
    private EntityManager entityManager;

    private final PurchaseRepository purchaseDao;
    private final CustomerRepository customerDao;
    private final RoomRepository roomDao;
    private final FriendRepository friendDao;
    private final QueryUtil queryUtil;

    public PurchaseService(PurchaseRepository purchaseDao, CustomerRepository customerDao, RoomRepository roomDao, FriendRepository friendDao, QueryUtil query) {
        this.purchaseDao = purchaseDao;
        this.customerDao = customerDao;
        this.roomDao = roomDao;
        this.friendDao = friendDao;
        this.queryUtil = query;
    }

    /**
     * 根据主键Id生成商户订单号,id流水加时间戳,id预设最大不到一千万
     */
    private String buildTradeNo(Integer Id) {
        StringBuilder idStr = new StringBuilder(Id.toString());
        int c = 7 - idStr.length();
        for (; c > 0; c--) {
            idStr.insert(0, "0");
        }
        return idStr + "-" + System.currentTimeMillis();
    }

    /**
     * 创建预约订单
     */
    public BoPurchase createPurchase(@Valid BoCreateParam param) {
        if (param.getAmount() == 0 || param.getCustomerID() == 0 || param.getRoomID() == 0 ||
                param.getBeginTime() == null || param.getEndTime() == null) {
            throw new BadParamException();
        }

        boolean roomExist = roomDao.existsRoomById(param.getRoomID());
        boolean customerExist = customerDao.existsCustomerById(param.getCustomerID());
        if (!roomExist || !customerExist) {
            throw new NotFoundException("找不到相应的房间或顾客");
        }
        Room room = new Room();
        room.setId(param.getRoomID());
        Customer customer = new Customer();
        customer.setId(param.getCustomerID());

        PurchaseRecord purchaseRecord = new PurchaseRecord();
        purchaseRecord.setAmount(param.getAmount());
        purchaseRecord.setBeginTime(param.getBeginTime());
        purchaseRecord.setEndTime(param.getEndTime());
        purchaseRecord.setRoom(room);
        purchaseRecord.setCustomer(customer);
        purchaseRecord.setState(PurchaseState.NotPayed);

        purchaseDao.save(purchaseRecord);
        purchaseRecord.setTradeNo(buildTradeNo(purchaseRecord.getId()));
        purchaseDao.save(purchaseRecord);
        return new BoPurchase(purchaseRecord);
    }

    /**
     * 获取交易订单
     */
    public BoPurchaseWithRoomAndCustomer getById(int id) {
        Optional<PurchaseRecord> optPurchase = purchaseDao.findById(id);
        if (!optPurchase.isPresent()) {
            throw new NotFoundException("没有找到订单");
        }
        return new BoPurchaseWithRoomAndCustomer(optPurchase.get());
    }

    /**
     * 各种条件分页查询
     */
    public BoPageInfo<BoPurchaseWithRoomAndCustomer> query(@NotNull BoQueryParam param, int pageNo, int pageSize) {
        if (pageNo > 0 && pageSize > 0) {
            PageHelper.startPage(pageNo, pageSize, "pid desc");
        }
        List<BoPurchaseWithRoomAndCustomer> list = queryUtil.queryPurchase(param);

        if (pageNo > 0 && pageSize > 0) {
            long pages = new PageInfo<>(list).getTotal();
            return new BoPageInfo<>(list, pages);
        } else {
            return new BoPageInfo<>(list, null);
        }
    }

    /**
     * 关键方法, 获取某房间在某天被占用的时间,用于支付检查
     */
    public List<BoRoomBookedTime> getOccupiedTimeOfByRoomAndDate(int roomId, LocalDateTime date) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        String dateStr = date.format(formatter);
        List<PurchaseRecord> records = purchaseDao.findPurchaseByRoomAndDate(roomId, dateStr);
        return records.stream().filter(r -> {
            if (r.getState() == PurchaseState.HasPayed) {
                return true;
            }
            if (r.getState() == PurchaseState.Used) {
                return true;
            }
            if (r.getPayingLock()) {
                return true;
            }
            return false;
        }).map(r -> new BoRoomBookedTime(r.getBeginTime(), r.getEndTime())).collect(Collectors.toList());
    }

    /**
     * 查询某房间在某天已预约或已使用或加锁的时间
     */
    public List<BoRoomBookedTime> getBookedTimeByRoomAndDate(@NotNull Integer roomId, @NotNull LocalDate date) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        String dateStr = formatter.format(date);
        List<PurchaseRecord> records = purchaseDao.findPurchaseByRoomAndDate(roomId, dateStr);
        return records.stream().filter(r -> {
            if (r.getState() == PurchaseState.HasPayed) {
                return true;
            }
            if (r.getState() == PurchaseState.Used) {
                return true;
            }
            if (r.getPayingLock()) {
                return true;
            }
            return false;
        }).map(r -> new BoRoomBookedTime(r.getBeginTime(), r.getEndTime())).collect(Collectors.toList());
    }


    /**
     * 在请求微信预支付id前给时间段加标记
     */
    public void lockPurchase(int purchaseID) {
        int count = purchaseDao.updatePayingLockById(purchaseID, true);
        if (count > 1) {
            throw new UpdateNotOneException("加锁订单超过一条");
        } else if (count < 1) {
            throw new UpdateNotOneException("没有任何订单被加锁");
        }
    }

    public void unLockPurchase(String outTradeNo) {
        int count = purchaseDao.updatePayingLockByTradeNo(outTradeNo, false);
        if (count > 1) {
            log.error("解锁订单超过一条");
        } else if (count < 1) {
            log.error("没有任何订单被解锁");
        }
    }


    /**
     * 关键方法,用于扫码开门
     * 获取当前日期某些用户对某些房间的所有已支付已使用或正在支付的订单
     */
    public List<BoPurchaseWithRoom> getPurchaseOfRoomsOfCustomerOfPayedOfToday(@NotEmpty Collection<Integer> roomIds, @NotEmpty Collection<Integer> customerIds) {

        List<PurchaseRecord> list = purchaseDao.findRecordByCustomersAndRoomsOfToday(roomIds, customerIds);

        return list.stream().filter(p -> p.getState() == PurchaseState.Used || p.getState() == PurchaseState.HasPayed)
                .map(BoPurchaseWithRoom::new).collect(Collectors.toList());
    }

    /**
     * 更新订单已使用
     */
    public int setPurchaseUsed(int purchaseId) {
        return purchaseDao.updatePurchaseState(purchaseId, PurchaseState.Used);
    }


    /**
     * 订单和朋友添加关联
     */
    public void inviteFriend(int purchaseId, Set<Integer> friendIds) {
        Optional<PurchaseRecord> optPurchase = purchaseDao.findById(purchaseId);
        if (!optPurchase.isPresent()) {
            throw new NotFoundException("没有找到订单");
        }
        Set<Friend> friends = friendDao.findByIdIn(friendIds);
        if (friendIds.size() != friends.size()) {
            throw new NotFoundException("未找到friendId列表里的所有数据");
        }

        PurchaseRecord purchaseRecord = optPurchase.get();
        List<Integer> friendsOfCustomer = purchaseRecord.getCustomer().getFriends().stream().map(Friend::getId).collect(Collectors.toList());
        Assert.isTrue(friendsOfCustomer.containsAll(friendIds), "该订单的顾客没有这些朋友");

        PurchaseState state = purchaseRecord.getState();
        if (state == PurchaseState.NotPayed) {
            throw new AddReferenceException("订单未支付");
        }
        if (state == PurchaseState.Used && purchaseRecord.getEndTime().isBefore(LocalDateTime.now())) {
            throw new AddReferenceException("订单已被消费");
        }

        purchaseRecord.getFriends().addAll(friends);
        entityManager.merge(purchaseRecord);
    }

    /**
     * 订单和朋友取消关联
     */
    public void cancelInviteFriend(int purchaseId, Set<Integer> friendIds) {
        Optional<PurchaseRecord> optPurchase = purchaseDao.findById(purchaseId);
        if (!optPurchase.isPresent()) {
            throw new NotFoundException("没有找到订单");
        }
        Set<Friend> friends = friendDao.findByIdIn(friendIds);
        if (friendIds.size() != friends.size()) {
            throw new NotFoundException("未找到friendId列表里的所有数据");
        }

        PurchaseRecord purchaseRecord = optPurchase.get();
        purchaseRecord.getFriends().removeAll(friends);
        entityManager.merge(purchaseRecord);

    }


    public List<BoFriend> getFriendsOfPurchase(int purchaseId) {
        Optional<PurchaseRecord> opt = purchaseDao.findById(purchaseId);
        if (!opt.isPresent()) {
            throw new NotFoundException();
        }
        return opt.get().getFriends().stream().map(BoFriend::new).collect(Collectors.toList());
    }

    public BoPurchase getPurchaseByTradeNo(@NotNull String tradeNo) {
        PurchaseRecord purchaseRecord = purchaseDao.findByTradeNo(tradeNo);
        if (purchaseRecord == null) {
            throw new NotFoundException();
        }
        return new BoPurchase(purchaseRecord);
    }

    /**
     * 修改支付成功的订单状态
     */
    public void setPurchasePayComplete(int id, String wxTradeID) {
        PurchaseRecord purchase = purchaseDao.findById(id).get();
        Assert.notNull(purchase, "微信通知相关,没有在系统里找到订单");
        purchase.setWxTradeID(wxTradeID);
        purchase.setState(PurchaseState.HasPayed);
        purchase.setPayTime(LocalDateTime.now());
        purchase.setPayingLock(false);
        purchaseDao.save(purchase);
    }

    public List<BoPurchase> getLockedPurchase() {
        List<PurchaseRecord> records = purchaseDao.findAllByPayingLockIsTrue();
        return records.stream().map(BoPurchase::new).collect(Collectors.toList());
    }

}
